﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Text;

namespace Tradex.ComponentModel {

    /// <summary>
    /// This class is a generic high performance queue for communicating between different
    /// threads. It allows registration of a callback action target which it will call to process
    /// the messages in a separate thread (using the ThreadPool to get access to a thread only when needed).
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711")] // Yeah, thanks - this IS a Queue.
    public class ActionQueue<T> {

        /// <summary>
        /// Public Constructor, optionally accepting the action to execute for new items.
        /// </summary>
        public ActionQueue() {
            _Items = new LinkedList<T>();
            _ActionCount = 0;
            _MaxLength = int.MaxValue;
            _MaxParallelism = 1;
        }

        /// <summary>
        ///  We use a linked list to store the items internally. This has the advantage of scaling without requiring
        ///  reallocation. This class is completely thread safe.
        ///  TODO: We may change that later to a paged buffer to reduce the amount of object allocations.
        /// </summary>
        LinkedList<T> _Items;

        /// <summary>
        /// This is the action callback registered for the queue.
        /// </summary>
        Action _Action;

        /// <summary>
        /// This counts the number of actions currently queued / in process.
        /// </summary>
        volatile int _ActionCount;

        /// <summary>
        /// The maximum length of the queue. if inserts are attempted for more than this length,
        /// the insert will block.
        /// </summary>
        volatile int _MaxLength;

        /// <summary>
        /// The maximum number of worker items used in parallel to work on this queue. The default is
        /// one. The number of work items queued at the same time will be the smaller of this and
        /// the number items in the queue.
        /// </summary>
        volatile int _MaxParallelism;

        /// <summary>
        /// Safely enqueues an item. Will queue a work item for handling the item as needed.
        /// </summary>
        /// <param name="item"></param>
        public void Enqueue(T item) {
            if (item == null) {
                return;
            }
            lock (_Items) {
                while (_Items.Count >= _MaxLength) {
                    // If the list is too long, we basically do have to wait until space gets free.
                    Monitor.Wait(_Items);
                }
                _Items.AddLast(item);
                Debug.Assert(_Items.Count <= _MaxLength);
                if (_Action != null && _ActionCount < Math.Min(_Items.Count, _MaxParallelism)) {
                    ThreadPool.QueueUserWorkItem(ThreadCallback);
                    _ActionCount += 1;
                    Debug.Assert(_ActionCount <= _MaxParallelism);
                }
                Monitor.PulseAll(_Items);
            }
        }

        /// <summary>
        /// Safely peeks the next item in the queue.
        /// </summary>
        /// <returns></returns>
        public T Peek() {
            T retval = default(T);
            lock (_Items) {
                if (_Items.Count != 0) {
                    retval = _Items.First.Value;
                }
            }
            return retval;
        }

        /// <summary>
        /// Safely peeks the next items in the queue, up to the length maxItems.
        /// </summary>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public T[] Peek(int maxItems) {
            int realcount;
            lock (_Items) {
                realcount = _Items.Count();
                realcount = Math.Min(realcount, maxItems);
                T[] retval = new T[realcount];
                LinkedListNode<T> node = _Items.First;
                for (int i = 0; i < realcount; i++) {
                    retval[i] = node.Value;
                    node = node.Next;
                }
                return retval;
            }
        }

        /// <summary>
        /// Dequeues the next item in the queue.
        /// </summary>
        /// <returns></returns>
        public bool Dequeue(out T item) {
            lock (_Items) {
                int currentCount = _Items.Count();
                if (currentCount != 0) {
                    item = _Items.First.Value;
                    _Items.RemoveFirst();
                    Debug.Assert(_Items.Count == currentCount - 1);
                    Monitor.PulseAll(_Items);
                    return true;
                }
            }
            item = default(T); ;
            return false;
        }

        /// <summary>
        /// Dequeues the next items in the queue, up to maxItems in number.
        /// </summary>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public T[] Dequeue(int maxItems) {
            lock (_Items) {
                int currentCount = _Items.Count();
                int realcount = Math.Min(currentCount, maxItems);
                T[] retval = new T[realcount];
                for (int i = 0; i < realcount; i++) {
                    retval[i] = _Items.First.Value;
                    _Items.RemoveFirst();
                }
                Debug.Assert (_Items.Count == currentCount - realcount);
                Monitor.PulseAll(_Items);
                return retval;
            }
        }

        /// <summary>
        /// Trims the first items in the queue. up to the length given in count.
        /// </summary>
        /// <param name="count"></param>
        public void TrimFirst(int count) {
            lock (_Items) {
                int realcount = Math.Min(_Items.Count(), count);
                for (int i = 0; i < realcount; i++) {
                    _Items.RemoveFirst();
                }
                if (_Items.Count < _MaxLength) {
                    Monitor.PulseAll(_Items);
                }
            }
        }

        /// <summary>
        /// Wait until the queue is idle. This is defined as having no items queued and no actions
        /// in progress..
        /// </summary>
        public void WaitForIdle() {
            lock (_Items) {
                Monitor.PulseAll(_Items);
                while (_Items.Count > 0 || _ActionCount > 0) {
                    Monitor.Wait(_Items);
                }
            }
        }

        /// <summary>
        /// Gets the number of items in the queue.
        /// </summary>
        public int Count {
            get {
                lock (_Items) {
                    return _Items.Count;
                }
            }
        }

        /// <summary>
        /// Gets of sets the action the queue should execute to handle items.
        /// </summary>
        public Action Action {
            get {
                lock (_Items) {
                    return _Action;
                }
            }
            set {
                lock (_Items) {
                    if (_Action == value) {
                        return;
                    }
                    _Action = value;
                    if (_Action != null) {
                        while (_ActionCount < _MaxParallelism && _ActionCount < _Items.Count) {
                            ThreadPool.QueueUserWorkItem(ThreadCallback);
                            _ActionCount += 1;
                        }
                    }
                    Monitor.PulseAll(_Items);
                }
            }
        }

        /// <summary>
        /// Gets or sets the maximum length of the queue. If an insert tries to push the queue further
        /// than this, it will block until items are removed from the queue.
        /// </summary>
        public int MaxLength {
            get {
                lock (_Items) {
                    return _MaxLength;
                };
            }
            set {
                lock (_Items) {
                    _MaxLength = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the maximum number of parallel workers that the queue will request.
        /// </summary>
        public int MaxParallelism {
            get {
                lock (_Items) {
                    return _MaxParallelism;
                }
            }
            set {
                lock (_Items) {
                    // We pulse everything waiting here, so that he number of threads can scale down.
                    Monitor.PulseAll(_Items);
                    _MaxParallelism = value;
                }
            }
        }

        /// <summary>
        /// Gets the current number of actins in progress or requested.
        /// </summary>
        public int ActionCount {
            get {
                lock (_Items) {
                    return _ActionCount;
                }
            }
        }

        void ThreadCallback(object state) {
            try {
                while (true) {
                    Action action;
                    lock (_Items) {
                        if (_Items.Count == 0) {
                            return;
                        }
                        action = _Action;
                        if (action == null) {
                            return;
                        }
                    }
                    action();
                }
                // TODO: what do we do in case of an exception?
            } finally {
                lock (_Items) {
                    _ActionCount--;
                    if (_Action != null && _ActionCount < Math.Min(_Items.Count, _MaxParallelism)) {
                        ThreadPool.QueueUserWorkItem(ThreadCallback);
                        _ActionCount++;
                        Debug.Assert(_ActionCount <= Math.Min(_Items.Count, _MaxParallelism));
                    }
                    Monitor.PulseAll(_Items);
                }
            }
        }

    }

}
